/*
 * Copyright (c) 2007, TopCoder, Inc. All rights reserved.
 */
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using TopCoder.Util.Calendar.Entry;

namespace TopCoder.WinForms.Controls.CalendarView.Views
{
    /// <summary>
    /// <para>This class draws the calendar data for a single year to the drawing panel. This class handles one
    /// year at a time, so the next and previous buttons move forward and back one year at a time.</para>
    /// </summary>
    /// <remarks>
    /// This class itself is mutable, as it inherits the mutability of its parent class.
    /// </remarks>
    /// <author>Ghostar</author>
    /// <author>yext</author>
    /// <author>argolite</author>
    /// <author>alex.dolin</author>
    /// <version>1.1</version>
    /// <copyright>Copyright (c) 2007, TopCoder, Inc. All rights reserved.</copyright>
    public class YearlyCalendarView : AbstractCalendarView
    {
        /// <summary>
        /// The minimal width of the drawing panel.
        /// </summary>
        private const int MIN_TOTAL_WIDTH = 1036;

        /// <summary>
        /// The minimal height of the drawing panel.
        /// </summary>
        private const int MIN_TOTAL_HEIGHT = 900;

        /// <summary>
        /// The number of days in each week.
        /// </summary>
        private const int DAYS_PER_WEEK = 7;

        /// <summary>
        /// The number of months in each row.
        /// </summary>
        private const int MONTHS_PER_ROW = 4;

        /// <summary>
        /// The number of months in each column.
        /// </summary>
        private const int MONTHS_PER_COLUMN = 3;

        /// <summary>
        /// The left and right margin of the drawing area.
        /// </summary>
        private const int LEFT_RIGHT_MARGIN = 14;

        /// <summary>
        /// The number of weeks displayed in each month.
        /// </summary>
        private const int WEEKS_PER_MONTH = 6;

        /// <summary>
        /// The height for the header row of each month.
        /// </summary>
        private const int MONTH_HEADER_HEIGHT = 60;

        /// <summary>
        /// The top margin of the drawing area.
        /// </summary>
        private const int TOP_MARGIN = 10;

        /// <summary>
        /// The string for formatting title text.
        /// </summary>
        private const string TITLE_DATE_FORMAT_STRING = "Y";

        /// <summary>
        /// The font size for the month name.
        /// </summary>
        private const int MONTH_NAME_FONT_SIZE = 10;

        /// <summary>
        /// The font size for the header text showing days in week.
        /// </summary>
        private const int HEADER_FONT_SIZE = 8;

        /// <summary>
        /// The row height for the header showing days in week.
        /// </summary>
        private const int HEADER_ROW_HEIGHT = 20;

        /// <summary>
        /// The text margin.
        /// </summary>
        private const int TEXT_MARGIN = 3;

        /// <summary>
        /// The row height for the text showing date.
        /// </summary>
        private const int DATE_LABEL_HEIGHT = 18;

        /// <summary>
        /// The default height of the color bars.
        /// </summary>
        private const int DEFAULT_BAR_HEIGHT = 5;

        /// <summary>
        /// <para>Represents the start date of the current view.</para>
        /// </summary>
        private DateTime startDate;

        /// <summary>
        /// <para>Represents the entry types sorted by the date.</para>
        /// </summary>
        private IDictionary<DateTime, IDictionary<CalendarEntryType, int>> sortedEntryTypes;

        /// <summary>
        /// <para>Represents the entries sorted by the date.</para>
        /// </summary>
        private IDictionary<DateTime, IList<CalendarEntry>> sortedEntries;

        /// <summary>
        /// Represents the name of the day view, to be used by the navigation panels.
        /// May be null, which would result in navigation not transfering to new view. Will not be empty.
        /// Managed by DayViewType property.
        /// </summary>
        private string dayViewType;

        /// <summary>
        /// Represents the name of the week view, to be used by the navigation panels.
        /// May be null, which would result in navigation not transfering to new view. Will not be empty.
        /// Managed by WeekViewType property.
        /// </summary>
        private string weekViewType;

        /// <summary>
        /// Represents the name of the month view, to be used by the navigation panels.
        /// May be null, which would result in navigation not transfering to new view. Will not be empty.
        /// Managed by MonthViewType property.
        /// </summary>
        private string monthViewType;

        /// <summary>
        /// Represents the names of columns to display. A null, by default, means all available columns will be used
        /// as they are found in the entry. An empty list means no columns will be displayed, and any given list will
        /// indicate the desired columns. If a given column is not found in the entry, it is ignored.
        /// The list is managed in the Columns property.
        /// </summary>
        private IList<string> columns;

        /// <summary>
        /// Indicates whether the data has changed since last view update.
        /// </summary>
        private bool dataChanged = false;

        /// <summary>
        /// <para>Use the specified date to filter the calendar entries.</para>
        /// </summary>
        private void FilterData()
        {
            startDate = DateOfInterest.Date.AddDays(-DateOfInterest.DayOfYear + 1);
            // Calculate the end date.
            DateTime endDate = startDate.AddYears(1).AddDays(-1);

            // Set the title text.
            TitleLabelText = startDate.ToString(TITLE_DATE_FORMAT_STRING) + " - " +
                             endDate.ToString(TITLE_DATE_FORMAT_STRING);

            // Select entries in that year.
            CurrentData = Helper.SelectEntriesByDateRange(AllData, startDate, endDate);

            // Sort the entry types by date.
            sortedEntryTypes = Helper.GetSortedEntryTypes(CurrentData);

            // Sort the entries by date.
            sortedEntries = Helper.GroupEntriesByDate(CurrentData);

            // Recreate all panels because current data has changed
            dataChanged = true;
            DrawingPanel.Invalidate();
        }

        /// <summary>
        /// Updates child panels of the DrawingPanel.
        /// If dataChanged is <c>true</c> then it will create
        /// new child panels.
        /// </summary>
        private void UpdatePanels()
        {
            DrawingPanel.SuspendLayout();
            if (dataChanged && DrawingPanel.Controls.Count > 0)
            {
                // Remove old navigation panels
                Helper.ReleaseChildControls(DrawingPanel);
                DrawingPanel.Controls.Clear();
            }

            Helper.AdjustPanelSize(HolderPanel, DrawingPanel, MIN_TOTAL_WIDTH, MIN_TOTAL_HEIGHT, ColumnWidthRendering);

            float columnWidth = (DrawingPanel.Width / MONTHS_PER_ROW - LEFT_RIGHT_MARGIN) / DAYS_PER_WEEK;
            float rowHeight = (DrawingPanel.Height / MONTHS_PER_COLUMN - MONTH_HEADER_HEIGHT) / WEEKS_PER_MONTH;

            DateTime viewDate = startDate;
            for (int i = 0; i < MONTHS_PER_COLUMN; i++)
            {
                for (int j = 0; j < MONTHS_PER_ROW; j++)
                {
                    Helper.DrawMonth(this, DrawingPanel, MonthViewType, WeekViewType, DayViewType, Columns,
                                     sortedEntries, sortedEntryTypes, viewDate,
                                     (LEFT_RIGHT_MARGIN + columnWidth * DAYS_PER_WEEK) * j + DAYS_PER_WEEK,
                                     (MONTH_HEADER_HEIGHT + rowHeight * WEEKS_PER_MONTH) * i + TOP_MARGIN,
                                     columnWidth, rowHeight, true, MONTH_NAME_FONT_SIZE, HEADER_FONT_SIZE,
                                     HEADER_ROW_HEIGHT,
                                     TEXT_MARGIN, DATE_LABEL_HEIGHT, DEFAULT_BAR_HEIGHT);
                    viewDate = viewDate.AddMonths(1);
                }
            }
            DrawingPanel.ResumeLayout();
            dataChanged = false;
        }

        /// <summary>
        /// Represents the date around which this view should be built. The resulting view must inlcude this date.
        /// By default, the current date is used.
        /// </summary>
        /// <value>The date around which this view should be built.</value>
        public override DateTime DateOfInterest
        {
            set
            {
                base.DateOfInterest = value;
                FilterData();
            }
        }

        /// <summary>
        /// <para>This method draws the drawing panel with the current calendar entries.</para>
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event, containing the graphic.</param>
        protected override void OnDrawingPanelPaint(object sender, PaintEventArgs e)
        {
            UpdatePanels();
        }

        /// <summary>
        /// Creates a new instance of this class.
        /// </summary>
        /// <param name="parent">The parent calendar view control.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="parent"/> is null.</exception>
        public YearlyCalendarView(CalendarViewControl parent)
            : base(parent)
        {
            FilterData();
        }

        /// <summary>
        /// Represents the name of the day view, to be used by the navigation panels.
        /// May be null, which would result in navigation not transfering to new view.
        /// Empty is set to null.
        /// </summary>
        /// <value>The name of the day view, to be used by the navigation panels.</value>
        public string DayViewType
        {
            get
            {
                return dayViewType;
            }
            set
            {
                dayViewType = Helper.GetNullIfEmpty(value);
            }
        }

        /// <summary>
        /// Represents the name of the week view, to be used by the navigation panels.
        /// May be null, which would result in navigation not transfering to new view.
        /// Empty is set to null.
        /// </summary>
        /// <value>The name of the week view, to be used by the navigation panels.</value>
        public string WeekViewType
        {
            get
            {
                return weekViewType;
            }
            set
            {
                weekViewType = Helper.GetNullIfEmpty(value);
            }
        }

        /// <summary>
        /// Represents the name of the month view, to be used by the navigation panels.
        /// May be null, which would result in navigation not transfering to new view.
        /// Empty is set to null.
        /// </summary>
        /// <value>The name of the month view, to be used by the navigation panels.</value>
        public string MonthViewType
        {
            get
            {
                return monthViewType;
            }
            set
            {
                monthViewType = Helper.GetNullIfEmpty(value);
            }
        }

        /// <summary>
        /// Represents the names of columns to display. A null, by default, means all available columns will be used
        /// as they are found in the entry. An empty list means no columns will be displayed, and any given list will
        /// indicate the desired columns. If a given column is not found in the entry, it is ignored.
        /// </summary>
        /// <value>The names of columns to display.</value>
        /// <exception cref="ArgumentException">If set to a list with null/empty strings.</exception>
        public IList<string> Columns
        {
            get
            {
                return Helper.GetStringListCopy(columns);
            }
            set
            {
                Helper.CheckStringListForNullAndEmptyElements(value, "Columns");
                columns = Helper.GetStringListCopy(value);
            }
        }

        /// <summary>
        /// <para>This method is called when the "Next" button is pressed on the header of the view.
        /// This method should get all calendar data for the year that is after the current year being shown.
        /// </para>
        /// </summary>
        /// <param name="sender">The next button that was pressed.</param>
        /// <param name="e">The event args.</param>
        protected override void OnNextButtonClick(object sender, EventArgs e)
        {
            DateOfInterest = startDate.AddYears(1);
        }

        /// <summary>
        /// <para>This method is called when the "Previous" button is pressed on the header of the view.
        /// This method should get all calendar data for the year that is before the current year being shown.
        /// </para>
        /// </summary>
        /// <param name="sender">The previous button that was pressed.</param>
        /// <param name="e">The event args.</param>
        protected override void OnPreviousButtonClick(object sender, EventArgs e)
        {
            DateOfInterest = startDate.AddYears(-1);
        }
    }
}